#include "globalconfig.h"
#include "xx/commdata.h"

#include <cstring>
#include <QDateTime>
#include <QFile>


#define SEND_PRINT 0
#define RECV_PRINT 0
#define SAVE_DEMO_DATA  0

std::atomic<bool> GlobalConfig::s_initialized{false};

GlobalConfig::GlobalConfig(QObject *parent)
    : QObject{parent}
{
    s_initialized = true;

    qRegisterMetaType<DevInfo>("DevInfo");
    qRegisterMetaType<uint8_t>("uint8_t");
    qRegisterMetaType<uint16_t>("uint16_t");
    qRegisterMetaType<std::vector<uint8_t>>("std::vector<uint8_t>");

    qRegisterMetaType<stDataCmps>("stDataCmps");
    qRegisterMetaType<stDataLimt>("stDataLimt");
    qRegisterMetaType<stDataNsgt>("stDataNsgt");
    qRegisterMetaType<stDataXover>("stDataXover");
    qRegisterMetaType<stDataEq>("stDataEq");
    qRegisterMetaType<stDataMixer>("stDataMixer");

    qRegisterMetaType<stCfgPeak>("stCfgPeak");


    auto &comm = DmComm::getInstance();
    comm.registerErrorHandler(std::bind(&GlobalConfig::error, this, std::placeholders::_1));
    comm.registerRecvHandler(std::bind(&GlobalConfig::recv, this, std::placeholders::_1));

    timer = new QTimer(this);
    connect(this, &GlobalConfig::initialized, timer, [=]() {
        timer->start(20);
    });
    connect(timer, &QTimer::timeout, this, &GlobalConfig::readPeak);
}

GlobalConfig::~GlobalConfig()
{
    s_initialized = false;
    close(m_info);
}

GlobalConfig &GlobalConfig::getInstance()
{
    static GlobalConfig instance;
    return instance;
}

std::vector<uint8_t> GlobalConfig::uint16ToBytes(const uint16_t &data)
{
    return { static_cast<uint8_t>(data & 0xFF), static_cast<uint8_t>(data >> 8 & 0xFF)};
}

void GlobalConfig::windowClosed()
{
    close(m_info);
}

void GlobalConfig::open(const DevInfo &info)
{
    close(m_info);
    auto &comm = DmComm::getInstance();

    comm.open(info);
    if (comm.isOpen()) {    // 打开成功
        m_info = info;
        m_flag.clear();
        readInfo();     // 设备信息
        readSystem(0);  // 系统数据
        readName(0);    // 名称
        loadScene(0);   // 当前场景数据
        emit connected(info);
        if (m_thread.joinable()) {
            return ;
        }
        // 启动工作线程
        s_atomic = true;
        m_thread = std::thread([this]() {
            while (s_atomic) {
                std::unique_lock<std::mutex> lock(m_mutex);

                m_cv.wait(lock, [this]() {
                    return !m_pool.empty() || !s_atomic;
                });

                if (!s_atomic) break;

                auto it = m_pool.begin();
                CmdMessage msg = it->second;
                m_pool.erase(it);
                lock.unlock();

                auto bytes = CommData::send(msg.cmd_1, msg.cmd_2, msg.cmd_3, msg.data);
#if SEND_PRINT
                qDebug()<<QString("%1 recv:%2 cmd: %3 %4 %5")
                                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                                .arg(msg.data.size(), 4)
                                .arg(msg.cmd_1, 2, 16, QLatin1Char('0'))
                                .arg(msg.cmd_2, 2, 16, QLatin1Char('0'))
                                .arg(msg.cmd_3, 2, 16, QLatin1Char('0'));
                // qDebug()<<QByteArray(reinterpret_cast<const char*>(msg.data.data()), static_cast<int>(msg.data.size())).toHex(' ').toUpper();
#endif
                if (CMD_ID_LOAD == msg.cmd_1 || CMD_ID_SAVE == msg.cmd_1 || CMD_ID_EXPORT == msg.cmd_1 || CMD_ID_IMPORT == msg.cmd_1) {
                    DmComm::getInstance().write(bytes, 3000);
                }
                else {
                    DmComm::getInstance().write(bytes);
                }
            }
        });
    }
}

void GlobalConfig::close(const DevInfo &info)
{
    if (info == m_info) {
        timer->stop();
        // 工作线程退出
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_pool.clear();
            s_atomic = false;
            m_cv.notify_one();
        }

        if (m_thread.joinable()) {
            m_thread.join();
        }
        // 关闭设备
        DmComm::getInstance().close();

        emit disconnected(info);

        m_flag.clear();
        m_info = {};
    }
}

void GlobalConfig::mute(uint8_t channel, uint8_t value)
{
    setChannel(CMD_ID_MUTE, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.mute = value; },
               [](PARAM_OUT& out, auto, auto value) { out.mute = value; });
}

void GlobalConfig::phase(uint8_t channel, uint8_t value)
{
    setChannel(CMD_ID_PHASE, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.phase = value; },
               [](PARAM_OUT& out, auto, auto value) { out.phase = value; });
}

void GlobalConfig::gain(uint8_t channel, uint16_t value)
{
    setChannel(CMD_ID_GAIN, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.gain = value; },
               [](PARAM_OUT& out, auto, auto value) { out.gain = value; });
}

void GlobalConfig::delay(uint8_t channel, uint16_t value)
{
    setChannel(CMD_ID_DELAY, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.delay = value; },
               [](PARAM_OUT& out, auto, auto value) { out.delay = value; });
}

void GlobalConfig::compressor(uint8_t channel, const stDataCmps &value)
{
    setChannel(CMD_ID_CPRS, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.cmps = value; },
               [](PARAM_OUT& out, auto, auto value) { out.cmps = value; });
}

void GlobalConfig::limiter(uint8_t channel, const stDataLimt &value)
{
    setChannel(CMD_ID_LIMT, channel, 0, value,
               [](PARAM_MUS& , auto, auto) { },
               [](PARAM_OUT& out, auto, auto value) { out.limt = value; });
}

void GlobalConfig::noiseGate(uint8_t channel, const stDataNsgt &value)
{
    setChannel(CMD_ID_NSGT, channel, 0, value,
               [](PARAM_MUS& mus, auto, auto value) { mus.nsgt = value; },
               [](PARAM_OUT& , auto, auto) { });
}

void GlobalConfig::xover(uint8_t channel, uint8_t type, const stDataXover &value)
{
    setChannel(CMD_ID_XOVER, channel, type, value,
               [](PARAM_MUS& mus, auto type, auto value) { mus.xover[type] = value; },
               [](PARAM_OUT& out, auto type, auto value) { out.xover[type] = value; });
}

void GlobalConfig::peq(uint8_t channel, uint8_t type, const stDataEq &value)
{
    setChannel(CMD_ID_PEQ, channel, type, value,
               [](PARAM_MUS& mus, auto type, auto value) { mus.eq[type] = value; },
               [](PARAM_OUT& out, auto type, auto value) { out.eq[type] = value; });
}

void GlobalConfig::mixer(uint8_t channel, uint8_t type, const stDataMixer &value)
{
    setChannel(CMD_ID_MIXER, channel, type, value,
               [](PARAM_MUS& , auto, auto ) {  },
               [](PARAM_OUT& out, auto type, auto value) { out.mixer[type] = value; });
}

void GlobalConfig::readInfo()
{
    write(CMD_ID_R_INFO, 0, 0, {});
}

void GlobalConfig::readPeak()
{
    write(CMD_ID_R_PEAK, 0, 0, {});
}

void GlobalConfig::readSystem(uint8_t id)
{
    write(CMD_ID_R_SYS, id, 0, {});
}

void GlobalConfig::writeSystem(uint8_t id, const std::vector<uint8_t> &data)
{
    write(CMD_ID_W_SYS, id, 0, data);
}

void GlobalConfig::readName(uint8_t id)
{
    write(CMD_ID_R_NAME, id, 0, {});
}

void GlobalConfig::writeDeviceName(const QString &value)
{
    write(CMD_ID_W_NAME, 0x00, 0, strToBytes(value));
    setName(m_cfgName.dev, value);
}

void GlobalConfig::writeSceneName(uint8_t index, const QString &value)
{
    write(CMD_ID_W_NAME, 0x01, index, strToBytes(value));
    setName(m_cfgName.user[index], value);
}

void GlobalConfig::writeInputName(uint8_t index, const QString &value)
{
    write(CMD_ID_W_NAME, 0x02, index, strToBytes(value));
    setName(m_cfgName.mus[index], value);
}

void GlobalConfig::writeOutputName(uint8_t index, const QString &value)
{
    write(CMD_ID_W_NAME, 0x03, index, strToBytes(value));
    setName(m_cfgName.out[index], value);
}

void GlobalConfig::loadScene(uint8_t index)
{
    write(CMD_ID_LOAD, 0, index, {});
}

void GlobalConfig::saveScene(uint8_t index)
{
    write(CMD_ID_SAVE, 0, index, {});
}

void GlobalConfig::exportScene(uint8_t index)
{
    write(CMD_ID_EXPORT, 0, index, {});
}

void GlobalConfig::importScene(uint8_t index, const std::vector<uint8_t> &data)
{
    write(CMD_ID_IMPORT, 0, index, data);
}

void GlobalConfig::copyChannel(uint8_t source, uint8_t target)
{
    if (source == target) {
        return;
    }
    write(CMD_ID_COPY, source, target, {});
}

void GlobalConfig::group(uint8_t channel, uint8_t index)
{
    if (channel <= MUS_CH4 && channel != (index + MUS_CH1)) {
        auto keys = m_input.keys(index);
        for (const auto& key : keys) {
            if (key != channel) {
                m_paramGroup.MusCh[channel - MUS_CH1] = m_paramGroup.MusCh[index];
                copyChannel(channel, key);
                break;
            }
        }
        m_input.insert(channel, index);
    }
    else if (channel >= OUT_CH1 && channel <= OUT_CH8 && channel != (index + OUT_CH1)) {
        auto keys = m_output.keys(index);
        for (const auto& key : keys) {
            if (key != channel) {
                m_paramGroup.OutCh[channel - OUT_CH1] = m_paramGroup.OutCh[index];
                copyChannel(channel, key);
                break;
            }
        }
        m_output.insert(channel, index);
    }
    emit refresh();
}

stCfgDev GlobalConfig::cfgDev() const
{
    return m_cfgDev;
}

stCfgData GlobalConfig::cfgData() const
{
    return m_cfgData;
}

stCfgName GlobalConfig::cfgName() const
{
    return m_cfgName;
}

PARAM_GROUP GlobalConfig::paramGroup() const
{
    return m_paramGroup;
}

void GlobalConfig::demo()
{
    read(m_cfgDev, ":/res/XXKJ-DSPM48.info");
    read(m_cfgData, ":/res/XXKJ-DSPM48.system");
    read(m_cfgName, ":/res/XXKJ-DSPM48.name");
    read(m_paramGroup, ":/res/XXKJ-DSPM48.scn");
}

void GlobalConfig::write(uint8_t cmd_1, uint8_t cmd_2, uint8_t cmd_3, const std::vector<uint8_t> &data)
{
    std::lock_guard<std::mutex> lock(m_mutex);

    CmdKey key = std::make_tuple(cmd_1, cmd_2, cmd_3);

    // 覆盖同类型数据：若已存在则更新为最新消息，不存在则新增
    m_pool[key] = {cmd_1, cmd_2, cmd_3, data};

    m_cv.notify_one();
}

void GlobalConfig::recv(const std::vector<uint8_t> &msg)
{
    // qDebug()<<msg;
    const auto opt = CommData::recv(msg);
    if (!opt) {  // 解析失败
        return;
    }
#if RECV_PRINT
    qDebug()<<QString("%1 recv:%2 cmd: %3 %4 %5")
                    .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                    .arg(opt->data.size(), 4)
                    .arg(opt->header.cmd_1, 2, 16, QLatin1Char('0'))
                    .arg(opt->header.cmd_2, 2, 16, QLatin1Char('0'))
                    .arg(opt->header.cmd_3, 2, 16, QLatin1Char('0'));
#endif
    switch (opt->header.cmd_1)
    {
    case CMD_ID_MUTE:      // 01 -> 静音
        break;
    case CMD_ID_PHASE:     // 02 -> 相位
        break;
    case CMD_ID_GAIN:      // 03 -> 增益
        break;
    case CMD_ID_DELAY:     // 04 -> 延时
        break;
    case CMD_ID_FBX:       // 05 -> 反馈
        break;
    case CMD_ID_CPRS:      // 06 -> 压缩
        break;
    case CMD_ID_LIMT:      // 07 -> 压限
        break;
    case CMD_ID_NSGT:      // 08 -> 噪声门
        break;
    case CMD_ID_XOVER:     // 09 -> 分频器
        break;
    case CMD_ID_PEQ:       // 0A -> 均衡器
        break;
    case CMD_ID_SRC:       // 0B -> 输入源选择
        break;
    case CMD_ID_MIXER:     // 0C -> 输出路由输入源
        break;
    case CMD_ID_DEQ:       // 0D -> 动态EQ
        break;
    case CMD_ID_FIR:       // 0E -> FIR滤波器
        break;
    case CMD_ID_TONE:      // 0F -> 变调
        break;
    case CMD_ID_EFF:       // 10 -> 效果
        break;
    case CMD_ID_R_INFO:    // 20 -> 读设备信息
        if (update(m_cfgDev, opt->data)) {
            m_flag.insert(CMD_ID_R_INFO);
            m_input.clear();
            m_output.clear();
            for (int i = 0; i < m_cfgDev.mus.tru_ch; ++i) {
                m_input.insert(i + MUS_CH1, i);
            }
            for (int i = 0; i < m_cfgDev.out.tru_ch; ++i) {
                m_output.insert(i + OUT_CH1, i);
            }

#if SAVE_DEMO_DATA
            save(std::vector<uint8_t>(msg.begin() + opt->begin, msg.begin() + opt->end), "XXKJ-DSPM48.info");
#endif
        }
        break;
    case CMD_ID_R_PEAK:    // 21 -> 读电平值等()
        if (update(m_peak, opt->data)) {
            emit peak(m_peak);
        }
        break;
    case CMD_ID_UPGRADE:   // 22 -> 系统升级
        break;
    case CMD_ID_R_SYS:     // 30 -> 读系统数据
        if (update(m_cfgData, opt->data)) {
            m_flag.insert(CMD_ID_R_SYS);
#if SAVE_DEMO_DATA
            save(std::vector<uint8_t>(msg.begin() + opt->begin, msg.begin() + opt->end), "XXKJ-DSPM48.system");
#endif
        }
        break;
    case CMD_ID_W_SYS:     // 31 -> 写系统数据
        break;
    case CMD_ID_R_NAME:    // 32 -> 读场景、通道名称
        if (update(m_cfgName, opt->data)) {
            m_flag.insert(CMD_ID_R_NAME);
#if SAVE_DEMO_DATA
            save(std::vector<uint8_t>(msg.begin() + opt->begin, msg.begin() + opt->end), "XXKJ-DSPM48.name");
#endif
        }
        break;
    case CMD_ID_W_NAME:    // 33 -> 写场景、通道名称
        switch (opt->header.cmd_2) {
        case 0x01:
            emit sceneNameUpdated();
            break;
        case 0x02:
            emit inputNameUpdated();
            break;
        case 0x03:
            emit outputNameUpdated();
            break;
        default:
            break;
        }

        break;
    case CMD_ID_R_CH:      // 34 -> 读通道数据
        break;
    case CMD_ID_W_CH:      // 35 -> 写通道数据
        break;
    case CMD_ID_R_FIR:     // 36 -> 读FIR数据命令
        break;
    case CMD_ID_W_FIR:     // 37 -> 写FIR数据命令
        break;
    case CMD_ID_LOAD:      // 38 -> 调用场景
        if (update(m_paramGroup, opt->data)) {
            m_flag.insert(CMD_ID_LOAD);
            emit loaded();
#if SAVE_DEMO_DATA
            save(std::vector<uint8_t>(msg.begin() + opt->begin, msg.begin() + opt->end), "XXKJ-DSPM48.scn");
#endif
        }
        break;
    case CMD_ID_SAVE:      // 39 -> 保存场景
        emit saved();
        break;
    case CMD_ID_EXPORT:    // 3A -> 导出场景
        emit exported(opt->header.cmd_3, opt->data);
        break;
    case CMD_ID_IMPORT:    // 3B -> 导入场景
        if (0 == opt->header.cmd_3) {
            loadScene(0);
        }
        else {
            emit imported(opt->header.cmd_3);
        }
        break;
    case CMD_ID_EXPORT_ALL:// 3C -> 导出全部场景
        break;
    case CMD_ID_IMPORT_ALL:// 3D -> 导入全部场景
        break;
    case CMD_ID_COPY:      // 50 -> 通道拷贝
        break;
    case CMD_ID_RST:       // 51 -> EQ复位
        break;
    case CMD_ID_BYPASS:    // 52 -> EQ直通
        break;
    case CMD_ID_W_GROUP:   // 53 -> PC把当前数据写到设备
        break;
    default:
        break;
    }
    if (4 == m_flag.size()) {
        emit initialized();
        m_flag.clear();
    }
}

void GlobalConfig::error(const int &err)
{
    if (err > 0) {
        emit disconnected(m_info);
        emit errorOccurred();
    }
}

void GlobalConfig::save(const std::vector<uint8_t> &data, const QString &fileName) const
{
    if (data.empty()) {
        return;
    }
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        return;
    }
    file.write(reinterpret_cast<const char*>(data.data()), static_cast<qint64>(data.size()));
    file.close();

    qDebug()<<fileName<<data.size();
    QStringList list;
    for(const uint8_t c : data) {
        list.append(QString("0x%1").arg(c, 2, 16, QChar('0')));
    }
    for (int i = 0; i < list.size(); i += 20) {
        if (i + 20 < list.size()) {
            qDebug().noquote()<<list.mid(i, 20).join(", ") + ",";
        }
        else {
            qDebug().noquote()<<list.mid(i, 20).join(", ");
        }
    }
}

std::vector<uint8_t> GlobalConfig::strToBytes(const QString &data) const
{
    auto bytes = data.toUtf8();
    return std::vector<uint8_t>(bytes.begin(), bytes.end());
}

template<class T>
std::vector<uint8_t> GlobalConfig::toBytes(const T &data) const
{
    std::vector<uint8_t> bytes;
    bytes.resize(sizeof(T));
    std::memcpy(bytes.data(), &data, sizeof(T));
    return bytes;
}

template<class T>
bool GlobalConfig::update(T &target, const std::vector<uint8_t> &source)
{
    if (sizeof(T) == source.size()) {
        std::memcpy(&target, source.data(), sizeof(T));
        return true;
    }
    return false;
}

template<typename T, typename MusSetter, typename OutSetter>
void GlobalConfig::setChannel(uint8_t cmd_1, uint8_t channel, uint8_t type, T value, MusSetter musSetter, OutSetter outSetter)
{
    std::vector<uint8_t> data = toBytes(value);

    if (channel >= MUS_CH1 && channel <= MUS_CH4) {
        auto group = m_input.value(channel);
        if (group) {
            auto keys = m_input.keys(group.value());
            for(const auto key : keys) {
                write(cmd_1, key, type, data);
                int idx = key - MUS_CH1;
                musSetter(m_paramGroup.MusCh[idx], type, value);
            }
        }
    }
    else if (channel >= OUT_CH1 && channel <= OUT_CH8) {
        auto group = m_output.value(channel);
        if (group) {
            auto keys = m_output.keys(group.value());
            for(const auto key : keys) {
                write(cmd_1, key, type, data);
                int idx = key - OUT_CH1;
                outSetter(m_paramGroup.OutCh[idx], type, value);
            }
        }
    }
    emit refresh();
}

template<class T>
void GlobalConfig::setName(T &target, const QString &source)
{
    std::strncpy(target, source.toStdString().c_str(), sizeof(target) - 1);
}

template<class T>
void GlobalConfig::read(T &target, const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly)) {
        return ;
    }

    QByteArray fileData = file.readAll();
    file.close();
    if (fileData.size() == sizeof(T)) {
        std::memcpy(&target, fileData.data(), fileData.size());
    }
}
